<?php


namespace EchoPHP\Log;

use EchoPHP\Facades\File;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Monolog\Processor\MemoryPeakUsageProcessor;
use Monolog\Processor\MemoryUsageProcessor;
use Monolog\Processor\ProcessIdProcessor;
use Monolog\Processor\UidProcessor;
use Monolog\Processor\WebProcessor;
use Psr\Log\LoggerInterface;
use InvalidArgumentException;
use Exception;

class LogManager implements LoggerInterface {

    /**
     * 日志错误级别常量
     */
    const DEBUG = 'debug';
    const INFO = 'info';
    const NOTICE = 'notice';
    const WARNING = 'warning';
    const ERROR = 'error';
    const CRITICAL = 'critical';
    const ALERT = 'alert';
    const EMERGENCY = 'emergency';

    /**
     * 应用程序实例
     * @var
     */
    protected $app;

    /**
     * 已解析的日志驱动实例集合
     * @var array
     */
    protected $channels = [];

    protected $monologInstances = [];

    public function __construct($app) {
        $this->app = $app;
    }

    public function channel($channel = null) {
        return $this->driver($channel);
    }

    /**
     * 获取驱动实例
     * @param null $driver 驱动名称
     * @return mixed
     * @throws Exception
     */
    public function driver($driver = null) {
        return $this->get($driver ?? $this->getDefaultDriver());
    }

    /**
     * 获取默认日志驱动名称
     * @return string
     * @throws Exception
     */
    public function getDefaultDriver() {
        return config('log.default_channel', 'web');
    }

    /**
     * 根据日志驱动名称获取或构造驱动实例
     * @param $name
     * @return mixed
     * @throws Exception
     */
    protected function get($name) {
        if (!isset($this->channels[$name])) {
            $this->channels[$name] = $this->resolve($name);
        }
        return $this->channels[$name];
    }

    /**
     * 获取日志驱动实例
     * @param $name
     * @return mixed
     * @throws Exception
     */
    protected function resolve($name) {
        $config = config('log.' . $name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Log Channel [{$name}] is not defined.");
        }

        foreach ($config as $driver => $driverConfig) {
            $driverMethod = 'create' . ucfirst($driver) . 'Driver';

            if (!method_exists($this, $driverMethod)) {
                throw new InvalidArgumentException("Driver [{$driver}] is not supported on channel [{$name}]");
            }
            $this->{$driverMethod}($name, $driverConfig);
        }

        return $this->getMonoLogInstance($name);
    }

    /**
     * 获取日志根目录
     * @return mixed
     * @throws Exception
     */
    protected function getRootLogPath() {
        $logPath = $this->app->make('path.log');
        if (!is_dir($logPath) && !File::makeDirectory($logPath, 0777, true)) {
            throw new Exception('log path not exist');
        }
        return $logPath;
    }

    /**
     * 获取或者实例化monolog日志实例
     * @param $channel
     * @return Logger
     */
    protected function getMonoLogInstance($channel) {
        if (!isset($this->monologInstances[$channel])) {
            $this->monologInstances[$channel] = new Logger($channel, [], [new UidProcessor()]);
        }
        return $this->monologInstances[$channel];
    }

    /**
     * 创建以小时为单位的日志文件
     * @param $channel
     * @param $config
     * @throws Exception
     */
    protected function createHourlyDriver($channel, $config) {
        $logger = $this->getMonoLogInstance($channel);
        //定义文件处理器
        $logPath = $this->getRootLogPath();
        $logPath = rtrim($logPath, '/') . '/' . $channel . '/' . date('Y') . '/' . date('Ym') . '/' . date('Ymd');
        File::makeDirectory($logPath, 0755, true);
        $logFile = $logPath . '/' . date('H') . '.log';
        $level = isset($config['level']) ? $config['level'] : 'debug';
        $handler = new StreamHandler($logFile, $level, true, 0777);
        if (isset($config['processor']) && is_array($config['processor'])) {
            foreach ($config['processor'] as $processor) {
                $this->setProcessor($handler, $processor);
            }
        }
        $this->setProcessor($handler, 'trimcontext');
        $logger->pushHandler($handler);
    }

    /**
     * 创建以天为单位的日志文件
     * @param $channel
     * @param $config
     * @throws Exception
     */
    protected function createDailyDriver($channel, $config) {
        $logger = $this->getMonoLogInstance($channel);
        $logPath = $this->getRootLogPath();
        $logPath = rtrim($logPath, '/') . '/' . $channel . '/' . date('Y') . '/' . date('Ym');
        File::makeDirectory($logPath, 0755, true);
        $logFile = $logPath . '/' . date('Ymd') . '.log';
        $level = isset($config['level']) ? $config['level'] : 'debug';
        $handler = new StreamHandler($logFile, $level, true, 0777);
        if (isset($config['processor']) && is_array($config['processor'])) {
            foreach ($config['processor'] as $processor) {
                $this->setProcessor($handler, $processor);
            }
        }
        $this->setProcessor($handler, 'trimcontext');
        $logger->pushHandler($handler);
    }

    /**
     * 创建单一文件的日志文件
     * @param $channel
     * @param $config
     * @throws Exception
     */
    protected function createSingleDriver($channel, $config) {
        $logger = $this->getMonoLogInstance($channel);
        $logPath = $this->getRootLogPath();

        $logPath = rtrim($logPath, '/') . '/' . $channel . '/single';
        File::makeDirectory($logPath, 0755, true);
        $logFile = $logPath . '/' . (isset($config['logname']) ? $config['logname'] : 'singloge.log');
        $level = isset($config['level']) ? $config['level'] : 'debug';
        $handler = new StreamHandler($logFile, $level, true, 0777);
        if (isset($config['processor']) && is_array($config['processor'])) {
            foreach ($config['processor'] as $processor) {
                $this->setProcessor($handler, $processor);
            }
        }
        $this->setProcessor($handler, 'trimcontext');
        $logger->pushHandler($handler);
    }

    /**
     * 创建实时通知日志驱动，默认alert及以上级别才会通知
     * @param $channel
     * @param $config
     * @throws Exception
     */
    protected function createRealtimeDriver($channel, $config) {
        $logger = $this->getMonoLogInstance($channel);
        $logPath = $this->getRootLogPath();
        $logPath = rtrim($logPath, '/') . '/realtime';
        File::makeDirectory($logPath, 0755, true);
        $logFile = $logPath . '/alert.log';
        $level = isset($config['level']) ? $config['level'] : 'alert';
        $handler = new StreamHandler($logFile, $level, true, 0777);
        //设置记录的日志内容的格式  暂时不考虑支持太多日志格式  比如可以直接记录成html格式用于邮件直接发送通知
        if (isset($config['processor']) && is_array($config['processor'])) {
            foreach ($config['processor'] as $processor) {
                $this->setProcessor($handler, $processor);
            }
        }
        $this->setProcessor($handler, 'trimcontext');
        $logger->pushHandler($handler);
    }

    /**
     * 设置日志处理器
     * @param AbstractProcessingHandler $handler
     * @param $processor
     */
    protected function setProcessor($handler, $processor) {
        switch ($processor) {
            case 'memory':
                $handler->pushProcessor(new MemoryUsageProcessor());
                $handler->pushProcessor(new MemoryPeakUsageProcessor());
                break;
            case 'process':
                $handler->pushProcessor(new ProcessIdProcessor());
                break;
            case 'webinfo':
                $extraField = [
                    'url' => 'REQUEST_URI',
                    'ip' => 'REMOTE_ADDR',
                    'ns_clientip' => 'HTTP_NS_CLIENTIP',
                    'http_method' => 'REQUEST_METHOD',
                    'server' => 'SERVER_NAME',
                    'referrer' => 'HTTP_REFERER',
                ];
                $handler->pushProcessor(new WebProcessor(null, $extraField));
                break;
            case 'clearcontext':
                //自定义清除上下文日志
                $handler->pushProcessor(function ($record) {
                    $record['context'] = [];
                    return $record;
                });
                break;
            case 'trimcontext':
                $trimField = (array)config('log.trim_field', []);
                if (!empty($trimField)) {
                    $handler->pushProcessor(function ($record) use ($trimField) {
                        foreach ($trimField as $field) {
                            if (isset($record['context'][$field])) {
                                $record['context'][$field] = '***';
                            }
                        }
                        return $record;
                    });
                }
                break;
        }
    }

    /**
     * 记录日志
     * @param mixed $level
     * @param string $message
     * @param array $context
     * @param string $channel
     */
    public function log($level, $message, array $context = array(), $channel = null) {
        $this->channel($channel)->log($level, $message, $context);
    }

    /**
     * 日志记录debug信息
     * @param string $message
     * @param array $context
     * @param null $channel
     */
    public function debug($message, array $context = array(), $channel = null) {
        $this->channel($channel)->debug($message, $context);
    }

    /**
     * 日志记录 info 信息 记录一般提示信息
     * @param string $message
     * @param array $context
     * @param null $channel
     */
    public function info($message, array $context = array(), $channel = null) {
        $this->channel($channel)->info($message, $context);
    }

    /**
     * 日志记录 notice 信息  通知级别，可以不处理
     * @param string $message
     * @param array $context
     * @param null $channel
     */
    public function notice($message, array $context = array(), $channel = null) {
        $this->channel($channel)->notice($message, $context);
    }

    /**
     * 日志记录 warning 信息  警告级别，可以不处理
     * @param string $message
     * @param array $context
     * @param null $channel
     */
    public function warning($message, array $context = array(), $channel = null) {
        $this->channel($channel)->warning($message, $context);
    }

    /**
     * 日志记录 error 信息  处理错误级别错误信息，可以暂时不处理
     * @param string $message
     * @param array $context
     * @param null $channel
     */
    public function error($message, array $context = array(), $channel = null) {
        $this->channel($channel)->error($message, $context);
    }

    /**
     * 日志记录 alert 信息， 非常严重级别错误信息，如memcache不可用，暂时不影响系统的正常使用，但需要立即处理
     * @param string $message
     * @param array $context
     * @param null $channel
     */
    public function alert($message, array $context = array(), $channel = null) {
        $this->channel($channel)->alert($message, $context);
    }

    /**
     * 日志记录 critical 信息 非常严重的错误级别信息，需要立即处理，如数据库不可访问等
     * @param string $message
     * @param array $context
     * @param null $channel
     */
    public function critical($message, array $context = array(), $channel = null) {
        $this->channel($channel)->critical($message, $context);
    }

    /**
     * 日志记录 emergency 信息  系统不可用级别信息
     * @param string $message
     * @param array $context
     * @param null $channel
     */
    public function emergency($message, array $context = array(), $channel = null) {
        $this->channel($channel)->emergency($message, $context);
    }


}